#include "global.h"
#include "read.h"
#include <cassert>
#include <cstdio>
#include <random>
#include <vector>
#include <algorithm>

double deltascore(int req_id,int num,int tgap)
{
	if(request[req_id].isbusy||request[req_id].is_done[num]) return 0;
	int x=timestamp-request[req_id].burntime;
	double ans=0;
	if(x+tgap>105) ans=max(105-x,0)*0.01;
	else if(x>10) ans=tgap*0.01;
	else ans=tgap*0.01-min(10-x,10)*0.005;
	return ans*(request[req_id].remain+1)/request[req_id].remain;
}
int read_instamp(int token,int round=1,int remain=0)
{
	int ans=0;
	if(remain==0&&token==80&&round>1) return (round-1)*int(G/16)+dis_canread[G];
	for(int R=0;R<=round;R++,remain=G)
	{
		while(remain-max((int)ceil(token*0.8),16)>=0)
		{
			ans++;
			remain-=( token=max((int)ceil(token*0.8),16) );
			if(token==16) ans+=remain/16,remain=0;
		}
		if(token==16) ans+=(G/16)*(round-R),R=round;
	}
	return ans;
}
int read_incycle(const DiskPoint &point,int CYCLE,int x)
{
	int dis_tox=point.dis_tox(x);
	if( dis_tox+64>G ) return read_instamp(80,CYCLE-1);
	else if( dis_tox==0 ) return read_instamp(point.pretoken,CYCLE);
	else return read_instamp(80,CYCLE-1,G-dis_tox);
}

double Decider::getscore_sum(int L,int R,double *sum) const
{
	if(R==L) return 0;
	R=(R==1?vdisk.V:R-1);
	if(R<L) return sum[R]+sum[vdisk.V]-sum[L-1];
	else return sum[R]-sum[L-1];
}
void Decider::scoresum_prepare()
{
	for(int subpart_id=0;subpart_id<3;subpart_id++)
	{
		score_sum[subpart_id][0]=pred_sum[subpart_id][0]=0;
		for(int j=1;j<=vdisk.V;j++) score_sum[subpart_id][j]=score_sum[subpart_id][j-1]+score[subpart_id][j];
		for(int j=1;j<=vdisk.V;j++) pred_sum[subpart_id][j]=pred_sum[subpart_id][j-1]+pred[subpart_id][j];
	}
}
void Decider::DiskScore_calc()
{
	for(int subpart_id=0;subpart_id<3;subpart_id++)
	{
		for(int j=1;j<=vdisk.V;j++)
		{
			score[subpart_id][j]=pred[subpart_id][j]=0;
			int object_id,num;
			vdisk.object_in(object_id,num,j,subpart_id);
			if(object_id == 0) continue;

			int size=object[object_id].size;
			//statisitic
			int sid=max(timestamp-105,lstvis[object_id][num]);
			if(!shouldbusy[object_id])
				for(int r=object[object_id].last_request_point;r&&!request[r].isbusy&&request[r].burntime>sid;r=request[r].prev_id)
					score[subpart_id][j]+=deltascore(r,num,CYCLE);
			//prediction
			if(Round==1)
			{
				pred[subpart_id][j]=1.2*readpred[object_id]*powd[timestamp-lstreq[object_id]]*0.01*(size+1)/size;
				score[subpart_id][j]+=pred[subpart_id][j]*CYCLE/2;
			}
			else
			{
				const vector<int> &list= object[object_id].requests;
				for(int i=object[object_id].it;i<list.size()&&request[list[i]].burntime<timestamp+CYCLE;i++)
					pred[subpart_id][j]+=deltascore(list[i],num,CYCLE);
				score[subpart_id][j]+=pred[subpart_id][j];
				pred[subpart_id][j]/=CYCLE/2.0;
			}
		}
	}
	scoresum_prepare();

	double tgap=CYCLE*1.1;
	int step=int(G/16)*tgap;
	if(Round==1) for(int subpart_id=0;subpart_id<3;subpart_id++)
	{
		double maxseg=0;
		for(int j=1;j<=vdisk.V;j++)
		{
			int L=vdisk.vpoint_away(j,-step+1),R=vdisk.vpoint_away(j,+step);
			double cursum=getscore_sum(L,R,score_sum[subpart_id])+getscore_sum(L,R,pred_sum[subpart_id])*tgap;
			maxseg=max(maxseg,cursum);
		}
		for(int j=1;j<=vdisk.V;j++)
		{
			int L=vdisk.vpoint_away(j,-step+1),R=vdisk.vpoint_away(j,+step);
			double cursum=getscore_sum(L,R,score_sum[subpart_id])+getscore_sum(L,R,pred_sum[subpart_id])*tgap*55;

			int object_id,num;
			vdisk.object_in(object_id,num,j,subpart_id);
			if(cursum < maxseg)
			{
				if(!shouldbusy[object_id])
					for(int r=object[object_id].last_request_point;r&&!request[r].isbusy&&request[r].burntime>=timestamp-105;r=request[r].prev_id)
						bebusy(r);
				shouldbusy[object_id]=1;
				score[subpart_id][j]=pred[subpart_id][j]=0;
			}
			else shouldbusy[object_id]=0;
		}
		scoresum_prepare();
	}
	// fprintf(stderr,"%lf\n",score_sum[0][vdisk.V]);
}

bool shouldbusy[MAX_OBJECT_NUM];
struct pack
{
	int pos,subid,val;
	pack(int x,int disk_id,int val)
	{
		int listpos= (disk_id-1)*(9*vdisk.partsize)+(x-1) ;
		pos=listpos%vdisk.V+1,subid=listpos/(3*vdisk.V);
		this->val=val;
		if(val<0) pos++;
		// if(!(subid>=0&&subid<3)) fprintf(stderr,"%d %d\n",disk_id,x);
		assert(subid>=0&&subid<3);
		assert(pos>=1&&pos<=vdisk.V+1);
	}
	bool operator < (const pack& p) const {return pos<p.pos;}
};
void add(vector<pack> & p,int l,int r,int disk_id) //[l,r]
{
	// if(timestamp==794) fprintf(stderr,"%d %d %d\n",l,r,disk_id);
	// r=min(r,vdisk.partsize*9);
	if(l>r) add(p,l,vdisk.partsize*9,disk_id),add(p,1,r,disk_id);
	else if(disk_id==4&&l<=vdisk.partsize*3&&r>vdisk.partsize*3)
		add(p,l,vdisk.partsize*3,disk_id),add(p,vdisk.partsize*3+1,r,disk_id);
	else if(disk_id==7&&l<=vdisk.partsize*6&&r>vdisk.partsize*6)
		add(p,l,vdisk.partsize*6,disk_id),add(p,vdisk.partsize*6+1,r,disk_id);
	else
	{
		p.push_back(pack(l,disk_id,1));
		p.push_back(pack(r,disk_id,-1));
	}
}
double Decider::getsum(DiskPoint *points,int *st,int pointnum,int extradis,double val,const double sum[3][MAX_VDISK_SIZE+1]) const
{
	vector<pack> p;

	for(int i=0;i<pointnum;i++)
	{
		int d=i/2+1,dis=read_incycle(points[i],CYCLE,st[i]);
		int _st=st[i],_ed=point_away(_st,dis-1+extradis);
		// if(extradis>0) _st=point_away(_st,dis);
		if(_st>9*vdisk.partsize) _st=1;
		if(_ed>9*vdisk.partsize) _ed=9*vdisk.partsize;
		add(p,_st,_ed,d);
	}
	sort(p.begin(),p.end());

	double ans=0;
	int lst[3]={0,0,0},layer[3]={0,0,0};
	for(auto _pack : p)
	{
		int &lstp=lst[_pack.subid],curp=_pack.pos;
		if(layer[_pack.subid]>0) ans+=sum[_pack.subid][curp-1]-sum[_pack.subid][lstp-1];
		layer[_pack.subid]+=_pack.val;
		lstp=curp;
	}
	for(int subid=0;subid<3;subid++)
		if(layer[subid]>0) ans+=sum[subid][vdisk.V]-sum[subid][lst[subid]-1];
	return ans*val;
}
double Decider::getans(DiskPoint *points,int *st,int pointnum) const
{
	return getsum(points,st,pointnum,0,1,score_sum)/*+
		getsum(points,st,pointnum,int(G/16),1.0*CYCLE,pred_sum)*/;
}

int jmplist[MAX_TIME_SLICING][MAX_DISK_NUM][2];
int Decider::simulateAnneal(int d,int pointid)
{
	const int maxstep=V;int step;
	static const double T0=1,Decay=0.99,Tk=1e-10;
	static int startpoint[MAX_DISK_NUM][2];
	if(Round==3) return jmplist[timestamp][d][pointid];
	if(d!=1||pointid!=0) return startpoint[d][pointid];

	int * const stpoints=&startpoint[1][0];
	DiskPoint * const points=&disk_point[1][0];
	int st[MAX_POINT_NUM];
	int pointnum=N*2;

	double ans=0,curans=0; // calculate initial score
	DiskScore_calc();
	for(int i=0;i<pointnum;i++) stpoints[i]=st[i]=points[i].pos;
	ans=curans=getans(points,st,pointnum);
	
	double T=T0;int p=0;
	// fprintf(stderr," %d %lf\n",timestamp,T);
	while(T>Tk)
	{
		step=max(1,(int)(pow(T/T0,0.3)*maxstep));
		double lstans=curans;
		int point_id=(p++)%pointnum,det=(mrand()&1?1:-1)*(mrand()%step+1);
		st[point_id]=point_away(st[point_id],det);
		curans=getans(points,st,pointnum);
		
		bool readyupd=(curans>lstans);
		if(!readyupd) st[point_id]=point_away(st[point_id],-det),curans=lstans;
		else if(curans>ans) 
		{
			ans=curans;
			for(int i=0;i<pointnum;i++) stpoints[i]=st[i];
		}
		T=T*Decay;
	}
	if(Round==2)
		for(int i=1;i<=N;i++)
			for(int j=0;j<2;j++) jmplist[timestamp][i][j]=startpoint[i][j];
		
	return startpoint[d][pointid];
}